// ValidationAspects
// Written by Mike Saunders
// Released under Microsoft Public License (Ms-PL)
using System;
using System.Linq;
using System.Reflection;
using NUnit.Framework;
using ValidationAspects;
using ValidationAspects.Sdk;

namespace TestValidationAspects
{
	[TestFixture]
	public class TestMethodValidationRegistry
	{
		[SetUp]
		public void SetUp()
		{
			TestHelpers.ResetFramework();
		}

		[Test]
		public void Test_FactoryDiscoveryFunc()
		{
			Assert.IsNull(typeof(Dummy).GetMethod("Method").ResolveValidator());

			MethodInfo method = typeof(Dummy).GetMethod("Method");
			bool wasCalled = false;
			method.ReplaceValidation<Dummy>((v, c) => { wasCalled = true; });
			method.ResolveValidator().Invoke(new Dummy(), new MockValidationContext());
			Assert.IsTrue(wasCalled);
		}

		#region Test ClearCache
		[Test]
		public void Test_ClearCache()
		{
			var method = typeof(Dummy).GetMethod("Method");
			method.ReplaceValidation<Dummy>((v, c) => { });

			Assert.IsNotNull(method.ResolveValidator());

			MethodValidationRegistry.ClearCache();

			Assert.IsNull(method.ResolveValidator());
		}
		#endregion

		#region Test RevertValidation
		[Test]
		[ExpectedException(typeof(ArgumentNullException))]
		public void Test_RevertValidation_throws_for_null_target_param()
		{
			MethodValidationRegistry.RevertValidation(null);
		}

		[Test]
		public void Test_RevertValidation_throws_for_static_method()
		{
			MethodInfo methodInfo = typeof(Dummy).GetMethod("StaticMethod");
			Test.Throws<ValidationException>(() => methodInfo.RevertValidation(),
			                                 string.Format("Object Validation cannot be applied to [method:{0}] [type:{1}]. Method is static or abstract.", methodInfo, methodInfo.DeclaringType));
		}

		[Test]
		public void Test_RevertValidation_throws_for_abstract_method()
		{
			MethodInfo methodInfo = typeof(BaseClass).GetMethod("Method");
			Test.Throws<ValidationException>(() => methodInfo.RevertValidation(),
			                                 string.Format("Object Validation cannot be applied to [method:{0}] [type:{1}]. Method is static or abstract.", methodInfo, methodInfo.DeclaringType));
		}

		[Test]
		public void Test_RevertValidation()
		{
			// test for coverage
			typeof(Dummy).GetMethod("Method").RevertValidation();
		}
		#endregion

		#region Test ReplaceValidation
		[Test]
		[ExpectedException(typeof(ArgumentNullException))]
		public void Test_ReplaceValidation_throws_for_null_target_param()
		{
			MethodValidationRegistry.ReplaceValidation(null, Enumerable.Empty<IValidatorFactory>());
		}

		[Test]
		[ExpectedException(typeof(ArgumentNullException))]
		public void Test_ReplaceValidation_throws_for_null_factories_param()
		{
			typeof(Dummy).GetMethod("Method").ReplaceValidation(null);
		}

		[Test]
		public void Test_ReplaceValidation_throws_for_static_method()
		{
			MethodInfo methodInfo = typeof(Dummy).GetMethod("StaticMethod");
			Test.Throws<ValidationException>(() => methodInfo.ReplaceValidation(new[] {new MockValidatorFactory<Dummy>()}),
			                                 string.Format("Object Validation cannot be applied to [method:{0}] [type:{1}]. Method is static or abstract.", methodInfo, methodInfo.DeclaringType));
		}

		[Test]
		public void Test_ReplaceValidation_throws_for_abstract_method()
		{
			MethodInfo methodInfo = typeof(BaseClass).GetMethod("Method");
			Test.Throws<ValidationException>(() => methodInfo.ReplaceValidation(new[] {new MockValidatorFactory<Dummy>()}),
			                                 string.Format("Object Validation cannot be applied to [method:{0}] [type:{1}]. Method is static or abstract.", methodInfo, methodInfo.DeclaringType));
		}

		[Test]
		public void Test_ReplaceValidation()
		{
			var method = typeof(Dummy).GetMethod("Method");

			MockValidatorFactory<Dummy> factory = new MockValidatorFactory<Dummy>();
			method.ReplaceValidation(new[] {factory});

			IValidationContext context = new MockValidationContext();
			Dummy value = new Dummy();
			method.ResolveValidator().Invoke(value, context);

			factory.TestState(value, context);
		}
		#endregion

		#region Test ReplaceValidationT
		[Test]
		[ExpectedException(typeof(ArgumentNullException))]
		public void Test_ReplaceValidationT_throws_for_null_target_param()
		{
			MethodValidationRegistry.ReplaceValidation<object>(null, (v, c) => { });
		}

		[Test]
		[ExpectedException(typeof(ArgumentNullException))]
		public void Test_ReplaceValidationT_throws_for_null_action_param()
		{
			typeof(Dummy).GetMethod("Method").ReplaceValidation<Dummy>(null);
		}

		[Test]
		public void Test_ReplaceValidationT_throws_for_static_method()
		{
			MethodInfo methodInfo = typeof(Dummy).GetMethod("StaticMethod");
			Test.Throws<ValidationException>(() => methodInfo.ReplaceValidation<Dummy>((v, c) => { }),
			                                 string.Format("Object Validation cannot be applied to [method:{0}] [type:{1}]. Method is static or abstract.", methodInfo, methodInfo.DeclaringType));
		}

		[Test]
		public void Test_ReplaceValidationT_throws_for_abstract_method()
		{
			MethodInfo methodInfo = typeof(BaseClass).GetMethod("Method");
			Test.Throws<ValidationException>(() => methodInfo.ReplaceValidation<Dummy>((v, c) => { }),
			                                 string.Format("Object Validation cannot be applied to [method:{0}] [type:{1}]. Method is static or abstract.", methodInfo, methodInfo.DeclaringType));
		}

		[Test]
		public void Test_ReplaceValidationT()
		{
			var method = typeof(Dummy).GetMethod("Method");

			Dummy valueFromValidator = null;
			IValidationContext contextFromValidator = null;
			method.ReplaceValidation<Dummy>((v, c) =>
			                                {
			                                	valueFromValidator = v;
			                                	contextFromValidator = c;
			                                });

			IValidationContext context = new MockValidationContext();
			Dummy value = new Dummy();
			method.ResolveValidator().Invoke(value, context);

			Assert.AreEqual(value, valueFromValidator);
			Assert.AreSame(context, contextFromValidator);
		}
		#endregion

		#region Test AddValidation
		[Test]
		[ExpectedException(typeof(ArgumentNullException))]
		public void Test_AddValidation_throws_for_null_target_param()
		{
			MethodValidationRegistry.AddValidation(null, Enumerable.Empty<IValidatorFactory>());
		}

		[Test]
		[ExpectedException(typeof(ArgumentNullException))]
		public void Test_AddValidation_throws_for_null_factories_param()
		{
			typeof(Dummy).GetMethod("Method").AddValidation(null);
		}

		[Test]
		public void Test_AddValidation_throws_for_static_method()
		{
			MethodInfo methodInfo = typeof(Dummy).GetMethod("StaticMethod");
			Test.Throws<ValidationException>(() => methodInfo.AddValidation(new[] {new MockValidatorFactory<Dummy>()}),
			                                 string.Format("Object Validation cannot be applied to [method:{0}] [type:{1}]. Method is static or abstract.", methodInfo, methodInfo.DeclaringType));
		}

		[Test]
		public void Test_AddValidation_throws_for_abstract_method()
		{
			MethodInfo methodInfo = typeof(BaseClass).GetMethod("Method");
			Test.Throws<ValidationException>(() => methodInfo.AddValidation(new[] {new MockValidatorFactory<Dummy>()}),
			                                 string.Format("Object Validation cannot be applied to [method:{0}] [type:{1}]. Method is static or abstract.", methodInfo, methodInfo.DeclaringType));
		}

		[Test]
		public void Test_AddValidation()
		{
			var method = typeof(Dummy).GetMethod("Method");

			MockValidatorFactory<Dummy> factory = new MockValidatorFactory<Dummy>();
			method.AddValidation(new[] {factory});

			IValidationContext context = new MockValidationContext();
			Dummy value = new Dummy();
			method.ResolveValidator().Invoke(value, context);

			factory.TestState(value, context);
		}
		#endregion

		#region Test AddValidationT
		[Test]
		[ExpectedException(typeof(ArgumentNullException))]
		public void Test_AddValidationT_throws_for_null_target_param()
		{
			MethodValidationRegistry.AddValidation<object>(null, (v, c) => { });
		}

		[Test]
		[ExpectedException(typeof(ArgumentNullException))]
		public void Test_AddValidationT_throws_for_null_actions_param()
		{
			typeof(Dummy).GetMethod("Method").AddValidation<Dummy>(null);
		}

		[Test]
		public void Test_AddValidationT_throws_for_static_method()
		{
			MethodInfo methodInfo = typeof(Dummy).GetMethod("StaticMethod");
			Test.Throws<ValidationException>(() => methodInfo.AddValidation<Dummy>((v, c) => { }),
			                                 string.Format("Object Validation cannot be applied to [method:{0}] [type:{1}]. Method is static or abstract.", methodInfo, methodInfo.DeclaringType));
		}

		[Test]
		public void Test_AddValidationT_throws_for_abstract_method()
		{
			MethodInfo methodInfo = typeof(BaseClass).GetMethod("Method");
			Test.Throws<ValidationException>(() => methodInfo.AddValidation<Dummy>((v, c) => { }),
			                                 string.Format("Object Validation cannot be applied to [method:{0}] [type:{1}]. Method is static or abstract.", methodInfo, methodInfo.DeclaringType));
		}

		[Test]
		public void Test_AddValidationT()
		{
			var method = typeof(Dummy).GetMethod("Method");

			Dummy valueFromValidator = null;
			IValidationContext contextFromValidator = null;
			method.AddValidation<Dummy>((v, c) =>
			                            {
			                            	valueFromValidator = v;
			                            	contextFromValidator = c;
			                            });

			IValidationContext context = new MockValidationContext();
			Dummy value = new Dummy();
			method.ResolveValidator().Invoke(value, context);

			Assert.AreEqual(value, valueFromValidator);
			Assert.AreSame(context, contextFromValidator);
		}
		#endregion

		#region Test ResolveValidator
		[Test]
		[ExpectedException(typeof(ArgumentNullException))]
		public void Test_ResolveValidator_throws_for_null_target_param()
		{
			MethodValidationRegistry.ResolveValidator(null);
		}

		[Test]
		public void Test_ResolveValidator_throws_if_validator_construction_fails()
		{
			MethodInfo methodInfo = typeof(Dummy).GetMethod("BrokenValidatorConstructorMethod");
			Test.Throws<ValidationException, ValidationException>(() => methodInfo.ResolveValidator(),
			                                                      string.Format("Failed to construct validator for [type:{0}] [method:{1}].", methodInfo.DeclaringType, methodInfo.Name));
		}

		[Test]
		public void Test_ResolveValidator_returns_null_for_no_validators()
		{
			var method = typeof(Dummy).GetMethod("Method");
			method.ReplaceValidation(Enumerable.Empty<IValidatorFactory>());
			Assert.IsNull(method.ResolveValidator());
		}

		[Test]
		public void Test_ResolveValidation_throws_for_static_method()
		{
			MethodInfo methodInfo = typeof(Dummy).GetMethod("StaticMethod");
			Test.Throws<ValidationException>(() => methodInfo.ResolveValidator(),
			                                 string.Format("Object Validation cannot be applied to [method:{0}] [type:{1}]. Method is static or abstract.", methodInfo, methodInfo.DeclaringType));
		}

		[Test]
		public void Test_ResolveValidation_throws_for_abstract_method()
		{
			MethodInfo methodInfo = typeof(BaseClass).GetMethod("Method");
			Test.Throws<ValidationException>(() => methodInfo.ResolveValidator(),
			                                 string.Format("Object Validation cannot be applied to [method:{0}] [type:{1}]. Method is static or abstract.", methodInfo, methodInfo.DeclaringType));
		}
		#endregion

		#region Test CanValidate
		[Test]
		[ExpectedException(typeof(ArgumentNullException))]
		public void Test_CanValidate_throws_for_null_target_param()
		{
			MethodValidationRegistry.CanValidate(null);
		}

		[Test]
		public void Test_CanValidate_returns_false_for_static_method()
		{
			MethodInfo methodInfo = typeof(Dummy).GetMethod("StaticMethod");
			Assert.IsFalse(methodInfo.CanValidate());
		}

		[Test]
		public void Test_CanValidate_returns_false_for_abstract_method()
		{
			MethodInfo methodInfo = typeof(BaseClass).GetMethod("Method");
			Assert.IsFalse(methodInfo.CanValidate());
		}

		[Test]
		public void Test_CanValidate_returns_true_for_validatable_property()
		{
			MethodInfo methodInfo = typeof(Dummy).GetMethod("Method");
			Assert.IsTrue(methodInfo.CanValidate());
		}
		#endregion

		#region Mocks
		public class Dummy
		{
			public void Method() {}

			[FailingValidatorConstructor]
			public void BrokenValidatorConstructorMethod() {}

			public static void StaticMethod() {}
		}

		public abstract class BaseClass
		{
			public abstract void Method();
		}


		[AttributeUsage(AttributeTargets.Method)]
		public class FailingValidatorConstructorAttribute : ValidatorAttribute
		{
			#region Overrides of ValidatorAttribute
			public override IValidatorFactory Factory
			{
				get { return new FailingConstructorFactory(); }
			}
			#endregion
		}

		public class FailingConstructorFactory : ValidatorFactoryBase<Dummy>
		{
			#region Overrides of ValidatorFactoryBase<Dummy2>
			protected override Action<Dummy, IValidationContext> ValidateAction
			{
				get { throw new InvalidOperationException(); }
			}
			#endregion
		}
		#endregion
	}
}